package org.darkbits.cyklon.machine;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.darkbits.cyklon.annotation.DefaultLeft;
import org.darkbits.cyklon.annotation.DefaultMono;
import org.darkbits.cyklon.annotation.DefaultRight;

public abstract class AbstractReflectedMachine implements Machine {
	private List<Property> propertyList;
	private List<Input> inputList;
	private List<Output> outputList;
	private Input leftInput, rightInput, monoInput;
	private Output leftOutput, rightOutput, monoOutput;
	
	public AbstractReflectedMachine() {
		this.propertyList = new ArrayList<Property>();		
		createPropertyList();
		createInputOutputLists();
	}
	
	private void createInputOutputLists() {
		List<MethodInput> inputs = new ArrayList<MethodInput>();
		List<MethodOutput> outputs = new ArrayList<MethodOutput>();
		
		Class myClass = this.getClass();
		Method methods[] = myClass.getMethods();
		
		for (Method method : methods) {
			org.darkbits.cyklon.annotation.Input inAnn = method.getAnnotation(org.darkbits.cyklon.annotation.Input.class);
			org.darkbits.cyklon.annotation.Output outAnn = method.getAnnotation(org.darkbits.cyklon.annotation.Output.class);
			
			if (inAnn != null) {
				String name = inAnn.name();
				
				if (name.equals("")) {
					if (method.getName().matches("^set[A-Z][A-Za-z]*")) {
						name = method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4); 
					} else {
						name = method.getName();
					}
				}
				
				MethodInput item = new MethodInput(this, name, inAnn.order(), method); 
				
				// TODO: Error message if more than one default
				if (method.getAnnotation(DefaultMono.class) != null) {
					monoInput = item;
				}
				
				if (method.getAnnotation(DefaultLeft.class) != null) {
					leftInput = item;
				}
				
				if (method.getAnnotation(DefaultRight.class) != null) {
					rightInput = item;
				}
				
				inputs.add(item);				
			}
			
			if (outAnn != null) {
				String name = outAnn.name();
				
				if (name.equals("")) {
					if (method.getName().matches("^get[A-Z][A-Za-z]*")) {
						name = method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4); 
					} else {
						name = method.getName();
					}
				}
				
				MethodOutput item = new MethodOutput(this, name, outAnn.order(), method);

				// TODO: Error message if more than one default
				if (method.getAnnotation(DefaultMono.class) != null) {
					monoOutput = item;
				}
				
				if (method.getAnnotation(DefaultLeft.class) != null) {
					leftOutput = item;
				}
				
				if (method.getAnnotation(DefaultRight.class) != null) {
					rightOutput = item;
				}
				
				outputs.add(item);				
			}
		}
		
		Collections.sort(inputs);
		Collections.sort(outputs);
		
		inputList = new ArrayList<Input>(inputs);
		outputList = new ArrayList<Output>(outputs);
	}
	
	private void createPropertyList() {
		List<MethodProperty> list = new ArrayList<MethodProperty>();
		
		Class myClass = this.getClass();
		Method methods[] = myClass.getMethods();
		
		for (Method method : methods) {
			org.darkbits.cyklon.annotation.Property ann = method.getAnnotation(org.darkbits.cyklon.annotation.Property.class);
			
			if (ann != null) {
				Method getter, setter = method, value;		
				
				if (!setter.getName().substring(0, 3).equals("set")) {
					// TODO: Handle error messages better
					System.out.println("Method marked as property does not start with 'set' " + setter.getName());
					continue;
				}
				
				String id = setter.getName().substring(3);
				String name;
				MethodProperty property;				
				
				try {
					getter = myClass.getMethod("get" + id, new Class[0]);				
				} catch (NoSuchMethodException e) {
					System.out.println("Unable to find a matching getter for property " + id);
					continue;
				}
				
				try {
					value = myClass.getMethod("valueOf" + id, new Class[0]);				
				} catch (NoSuchMethodException e) {
					value = getter;
				}
				
				if (ann.name().equals("")) {
					name = id.replaceAll("[A-Z]", " $0");
					name = name.substring(1, 2).toUpperCase() + name.substring(2);
				} else {
					name = ann.name();
				}
				
				try {
					property = new MethodProperty(this, name, ann.order(), getter, setter, value, ann.min(), ann.max());
				} catch (TypeMismatchException e) {
					System.out.println("Type mismatch for getter and setter for property " + id);
					continue;
				}
							
				list.add(property);
			}
		}
		
		Collections.sort(list);
		propertyList = new ArrayList<Property>(list);
	}

	public List<Property> getProperties() {
		return Collections.unmodifiableList(propertyList);
	}

	public List<Input> getInputs() {
		return Collections.unmodifiableList(inputList);
	}
	
	public List<Output> getOutputs() {
		return Collections.unmodifiableList(outputList);
	}

	public Input getLeftInput() {
		return leftInput;
	}

	public Output getLeftOutput() {
		return leftOutput;
	}

	public Input getMonoInput() {
		return monoInput;
	}

	public Output getMonoOutput() {
		return monoOutput;
	}

	public Input getRightInput() {
		return rightInput;
	}

	public Output getRightOutput() {
		return rightOutput;
	}	
}
